package com.ybbase.framework.config.mybatisplus;

import com.ybbase.framework.common.constant.ConfigConstant;
import com.ybbase.framework.common.util.ReflectionUtils;
import com.ybbase.framework.common.util.SpringUtil;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.io.StringReader;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * 创建自定义拦截器
 *
 * @author cg
 * @date 2020-01-27 22:07
 * @since cloud2.0
 */
@Component
@Intercepts({@Signature(method = "query", type = Executor.class,
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class MybatisDataAuthInterceptor implements Interceptor, ApplicationContextAware {
    private final static Logger logger = LoggerFactory.getLogger(MybatisDataAuthInterceptor.class);

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    @Override
    public Object intercept(Invocation arg0) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) arg0.getArgs()[0];
        // 只对查询sql拦截
        if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
            return arg0.proceed();
        }
        // 注解逻辑判断 添加注解了才拦截追加
        Class<?> classType = Class.forName(mappedStatement.getId().substring(0,
                mappedStatement.getId().lastIndexOf(".")));
        String mName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1,mappedStatement.getId().length());
        for (Method method : classType.getDeclaredMethods()) {
            if (method.isAnnotationPresent(DataPermission.class) && mName.equals(method.getName())) {
                /**
                 * 查找标识了该注解 的实现 类
                 */
                DataPermission action = method.getAnnotation(DataPermission.class);
                try {
                    /**
                     * 反射获取业务 sql
                     */
                    ReflectionUtils reflectionUtils = ReflectionUtils.getInstance();
                    Object obj = reflectionUtils.getAutoObj(ConfigConstant.AUTH_DATA_PERMISSION_CLASS);
                    String whereSql = (String) ReflectionUtils.invoke(obj, action.method(), action.tableAlias(),action.dataMethod());
                    if (!StringUtils.isEmpty(whereSql) && !"null".equalsIgnoreCase(whereSql)) {
                        Object parameter = null;
                        if (arg0.getArgs().length > 1) {
                            parameter = arg0.getArgs()[1];
                        }
                        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
                        MappedStatement newStatement = newMappedStatement(mappedStatement, new BoundSqlSqlSource(boundSql));
                        MetaObject msObject = MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),
                                new DefaultReflectorFactory());
                        /**
                         * 通过JSqlParser解析 原有sql,追加sql条件
                         */
                        CCJSqlParserManager parserManager = new CCJSqlParserManager();
                        Select select = (Select) parserManager.parse(new StringReader(boundSql.getSql()));
                        PlainSelect selectBody = (PlainSelect) select.getSelectBody();
                        Expression whereExpression = CCJSqlParserUtil.parseCondExpression(whereSql);
                        selectBody.setWhere(new AndExpression(selectBody.getWhere(), new Parenthesis(whereExpression)));
                        /**
                         * 修改sql
                         */
                        msObject.setValue("sqlSource.boundSql.sql", selectBody.toString().toUpperCase().replace("NULL AND", ""));
                        arg0.getArgs()[0] = newStatement;
                        logger.info("Interceptor sql:" + selectBody.toString());
                        break;
                    }
                } catch (Exception e) {
                    logger.error(null, e);
                }
                break;
            }
        }
        return arg0.proceed();
    }

    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if ((ms.getKeyProperties() != null) && (ms.getKeyProperties().length != 0)) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    /**
     * 当目标类是Executor类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的次数
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;
    }
    @Override
    public void setProperties(Properties arg0) {
        // TODO Auto-generated method stub
    }
    class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;
        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }
        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}
